<?php

/**
 * This file is part of Webman AI.
 *
 * @author    walkor<walkor@workerman.net>
 * @copyright walkor<walkor@workerman.net>
 * @link      http://www.workerman.net/
 */

namespace plugin\ai\app\handler\driver;

use support\exception\BusinessException;
use Throwable;
use Workerman\Connection\AsyncTcpConnection;
use Workerman\Http\Response;

class Spark extends Base
{

    /**
     * @var string api地址
     */
    protected $api = 'ws://spark-api.xf-yun.com';


    /**
     * @var string appid
     */
    protected $appid = '';

    /**
     * @var string apiSecret
     */
    protected $apiSecret = '';

    /**
     * @var float
     */
    protected $version = 3.1;

    /**
     * @var mixed|string
     */
    protected $domain = '';

    public static $versionMap = [
        '1.5' => ['1.1', 'general'],
        'spark-lite' => ['1.1', 'general'],
        'spark lite' => ['1.1', 'general'],
        '2' => ['2.1', 'generalv2'],
        '2.0' => ['2.1', 'generalv2'],
        '2.1' => ['2.1', 'generalv2'],
        '3' => ['3.1', 'generalv3'],
        '3.0' => ['3.1', 'generalv3'],
        '3.1' => ['3.1', 'generalv3'],
        'spark-pro' => ['3.1', 'generalv3'],
        'spark pro' => ['3.1', 'generalv3'],
        '3.5' => ['3.5', 'generalv3.5'],
        'spark max' => ['3.5', 'generalv3.5'],
        '4.0' => ['4.0', '4.0Ultra'],
        '4.0ultra' => ['4.0', '4.0Ultra'],
        '4.0 ultra' => ['4.0', '4.0Ultra'],
        'spark4.0 ultra' => ['4.0', '4.0Ultra'],
    ];

    /**
     * @param array $options
     */
    public function __construct(array $options = [])
    {
        parent::__construct($options);
        $this->apiSecret = $options['secretKey'] ?? '';
        $this->appid = $options['appid'] ?? '';
        $this->version = $options['version'] ?? '3.1';
        $this->domain = $options['domain'] ?? '';
    }

    /**
     * 对话
     * @param $data
     * @param $options
     * @return void
     * @throws BusinessException
     * @throws Throwable
     */
    public function completions($data, $options)
    {
        [$addrVersion, $domain] = $this->getVersion();
        $addr = "$this->api:443/v{$addrVersion}/chat";
        $authAddr = $addr . '?' . static::auth($this->appid, $this->apikey, $this->apiSecret, $addr);
        $this->api = $authAddr;
        $con = new AsyncTcpConnection($authAddr, ['ssl' => [
            'verify_peer' => false,
        ]]);
        $con->transport = 'ssl';
        $buffer = $this->buildData($this->appid, $domain, $data['messages'], $data['temperature'] ?? null, $data['maxTokens'] ?? null);
        $con->send($buffer);
        $con->buffer = '';
        // 失败时
        $con->onError = function ($con, $code, $msg) use ($options) {
            if ($cb = $options['stream'] ?? false) {
                $cb(json_encode(['error' => ['code' => $code, 'message' => $msg]]));
            }
        };
        // api接口返回数据时
        $con->onMessage = function ($con, $buffer) use ($options) {
            $con->buffer .= ($buffer . "\n");
            $json = json_decode($buffer, true);
            if (!$json) {
                return;
            }
            $data = array_merge(['content' => ''], $json);
            $data['content'] = $json['payload']['choices']['text'][0]['content'] ?? "";
            if (!empty($json['header']['code'])) {
                $data['error'] = ['code' => $json['header']['code'], 'message' => $json['header']['message'] ?? ''];
            }
            if ($cb = $options['stream'] ?? false) {
                $cb($data);
            }
        };
        // 连接断开时，触发当前连接上的onClose回调
        $con->onClose = function ($con) use ($options) {
            $options['complete'](static::formatResponse($con->buffer), (new Response(200, [], $con->buffer)));
        };
        $con->connect();
    }

    protected function getVersion(): array
    {
        $version = strtolower($this->version);
        if (isset(static::$versionMap[$version])) {
            return static::$versionMap[$version];
        }
        return [$version, $this->domain];
    }

    public static function auth($appid, $apikey, $apiSecret, $addr)
    {
        if (!$apiSecret || !$apikey || !$appid) {
            throw new BusinessException('讯飞配置不正确');
        }
        $ul = parse_url($addr);
        $rfc1123_format = gmdate("D, d M Y H:i:s \G\M\T", time());
        $signString = array("host: " . $ul["host"], "date: " . $rfc1123_format,  "GET " . $ul["path"] . " HTTP/1.1");
        $sign = implode("\n", $signString);
        $sha = hash_hmac('sha256', $sign, $apiSecret,true);
        $signature_sha_base64 = base64_encode($sha);
        $authUrl = "api_key=\"$apikey\", algorithm=\"hmac-sha256\", headers=\"host date request-line\", signature=\"$signature_sha_base64\"";
        $authUrlBase64 = base64_encode($authUrl);
        return http_build_query([
            'authorization' => $authUrlBase64,
            'date' => $rfc1123_format,
            'host' => $ul['host']
        ]);
    }

    public function buildData($appid, $domain, $messages, $temperature = null, $maxTokens = null)
    {
        $header = [
            "app_id" => $appid,
            "uid" => "0"
        ];
        $parameter = [
            "chat" => [
                "domain" => $domain,
            ]
        ];
        if ($temperature) {
            $parameter['chat']['temperature'] = $temperature;
        }
        if ($maxTokens) {
            $parameter['chat']['max_tokens'] = $maxTokens;
        }
        $payload = [
            "message" => [
                "text" => $messages
            ]
        ];
        return json_encode([
            "header" => $header,
            "parameter" => $parameter,
            "payload" => $payload
        ]);
    }

    public static function formatResponse($buffer): string
    {
        $chunks = explode("\n", $buffer);
        $content = '';
        foreach ($chunks as $chunk) {
            if ($chunk === "") {
                continue;
            }
            try {
                $data = json_decode($chunk, true);
                if (!empty($data['header']['code'])) {
                    $content .= $data['header']['message'] ?? "";
                } else {
                    $content .= $data['payload']['choices']['text'][0]['content'] ?? "";
                }
            } catch (Throwable $e) {
                echo $e;
            }
        }
        return $content;
    }

}